2-3 高可用实践:优化测试动态grpc客户端切换
调试动态 Client 切换
上一节创建了 ConsulService 和定时健康检查机制,本节通过断点调试验证动态 Client 切换的完整流程。
调试前的准备
- 确认 ConsulModule.forRoot 配置正确:options 注入通过
inject('CONSUL_OPTIONS')获取 - 确认 user 微服务未启动:先不启动后端服务,观察重试逻辑
- 在关键位置设置断点:
updateService()、initClient()、healthCheck()
调试流程
Gateway 启动 → onModuleInit()
└── updateService()
├── Consul.agent.service.list() → 返回空(后端未启动)
├── 没有匹配的服务 → 进入 catch
└── 设置 5 秒后重试
└── 5 秒后再次 updateService()
├── 仍然失败 → 继续重试
└── 成功 → initClient() → 发布 Client
text
验证要点
| 验证项 | 预期行为 |
|---|---|
| 服务不可用时 | 进入 catch,5 秒后重试,不会崩溃 |
| 服务启动后 | 下一次重试时获取到服务实例 |
| Client 创建 | 使用新的 address:port 创建 gRPC Client |
| 请求恢复 | Client 更新后,正常响应请求 |
ConsulModule.forRoot 动态模块配置
配置结构
// app.module.ts
@Module({
imports: [
ScheduleModule.forRoot(),
ConsulModule.forRoot({
host: 'consul-server-address',
port: 8500,
serviceName: 'user-service',
protoPackage: 'user',
}),
],
})
export class AppModule implements OnModuleInit {}
typescript
options 注入方式
在 ConsulService 中通过 @Inject() 获取 forRoot 传入的配置:
@Injectable()
export class ConsulService implements OnModuleInit {
private readonly options: ConsulModuleOptions;
constructor(
@Inject('CONSUL_OPTIONS') options: ConsulModuleOptions,
) {
this.options = options;
}
async onModuleInit() {
await this.updateService();
}
}
typescript
常见问题排查
| 问题 | 原因 | 解决方式 |
|---|---|---|
CONSUL_OPTIONS 未定义 | forRoot 未正确配置 provider | 检查 provide: 'CONSUL_OPTIONS' 是否存在 |
| Consul 连接超时 | host/port 配置错误 | 确认 Consul Server 地址和端口 |
| 服务列表为空 | 目标微服务未注册到 Consul | 先启动目标微服务 |
| Client 创建后请求失败 | address 或 port 不正确 | 检查 Consul 注册的地址信息 |
健康检查频率测试
为了方便调试,可以将 Cron 表达式设置为每分钟执行一次:
// 正式环境建议 30 秒
@Cron('*/30 * * * * *')
// 调试阶段可设置为每分钟
@Cron('* * * * *')
typescript
测试场景
场景一:服务正常启动
1. 启动 user 微服务 → 注册到 Consul
2. 启动 Gateway → ConsulService 发现 user-service
3. 创建 gRPC Client → 请求正常响应
text
场景二:服务中途宕机
1. 正常运行中,停止 user 微服务
2. 定时健康检查发现服务不可用
3. 自动调用 updateService() 尝试获取其他实例
4. 如有其他健康实例 → 切换到新实例
5. 如无健康实例 → 进入重试循环
text
场景三:服务恢复
1. 重试循环中,重新启动 user 微服务
2. 下一次 updateService() 获取到新服务实例
3. initClient() 创建新 Client
4. 请求恢复正常
text
调试技巧
断点位置建议
| 位置 | 作用 |
|---|---|
updateService() 入口 | 观察每次服务发现的过程 |
initClient() 内部 | 确认 Client 创建参数是否正确 |
healthCheck() 内部 | 验证定时检查是否正常触发 |
| catch 块 | 确认错误处理和重试逻辑 |
日志辅助
在关键位置添加日志,方便观察运行状态:
async updateService() {
try {
const services = await this.consul.agent.service.list();
console.log('[ConsulService] 服务列表:', Object.keys(services));
// ...
} catch (error) {
console.log('[ConsulService] 获取服务失败,5秒后重试...');
this.timeoutControl = setTimeout(() => this.updateService(), 5000);
}
}
typescript
参考资源
- NestJS Dynamic Modules - 动态模块详解
- Consul Health Checks - Consul 健康检查机制
↑